). Writes errors to the Ajax console if the element or
* manager can not be resolved.
*
* @param zoneElement
* zone element (id or instance)
* @return Tapestry.ZoneManager instance for zone, or null if not found
*/
findZoneManagerForZone : function(zoneElement) {
var element = $(zoneElement);
if (!element) {
Tapestry.error(Tapestry.Messages.missingZone, {
id : zoneElement
});
return null;
}
var manager = $T(element).zoneManager;
if (!manager) {
Tapestry.error(Tapestry.Messages.noZoneManager, element);
return null;
}
return manager;
},
/**
* Used to reconstruct a complete URL from a path that is (or may be)
* relative to window.location. This is used when determining if a
* JavaScript library or CSS stylesheet has already been loaded. Recognizes
* complete URLs (which are returned unchanged), otherwise the URLs are
* expected to be absolute paths.
*
* @param path
* @return complete URL as string
*/
rebuildURL : function(path) {
if (path.match(/^https?:/)) {
return path;
}
if (!path.startsWith("/")) {
Tapestry.error(Tapestry.Messages.pathDoesNotStartWithSlash, {
path : path
});
return path;
}
if (! Tapestry.buildURL) {
var l = window.location;
Tapestry.buildURL = l.protocol + "//" + l.host;
}
return Tapestry.buildURL + path;
},
stripToLastSlash : function(URL) {
var slashx = URL.lastIndexOf("/");
return URL.substring(0, slashx + 1);
},
/**
* Convert a user-provided localized number to an ordinary number (not a
* string). Removes seperators and leading/trailing whitespace. Disallows
* the decimal point if isInteger is true.
*
* @param number
* string provided by user
* @param isInteger
* if true, disallow decimal point
*/
formatLocalizedNumber : function(number, isInteger) {
/*
* We convert from localized string to a canonical string, stripping out
* group seperators (normally commas). If isInteger is true, we don't
* allow a decimal point.
*/
var minus = Tapestry.decimalFormatSymbols.minusSign;
var grouping = Tapestry.decimalFormatSymbols.groupingSeparator;
var decimal = Tapestry.decimalFormatSymbols.decimalSeparator;
var canonical = "";
number.strip().toArray().each(function(ch) {
if (ch == minus) {
canonical += "-";
return;
}
if (ch == grouping) {
return;
}
if (ch == decimal) {
if (isInteger)
throw Tapestry.Messages.notAnInteger;
ch = ".";
} else if (ch < "0" || ch > "9")
throw Tapestry.Messages.invalidCharacter;
canonical += ch;
});
return Number(canonical);
},
/**
* Marks a number of script libraries as loaded; this is used with virtual
* scripts (which combine multiple actual scripts). This is necessary so
* that subsequent Ajax requests do not load scripts that have already been
* loaded
*
* @param scripts
* array of script paths
*/
markScriptLibrariesLoaded : function(scripts) {
$(scripts).each(function(script) {
var complete = Tapestry.rebuildURL(script);
Tapestry.ScriptManager.virtualScripts.push(complete);
});
},
/**
* Creates a clone of the indicated element, but with the alternate tag
* name. Attributes of the original node are copied to the new node. Tag
* names should be all upper-case. The content of the original element is
* copied to the new element and the original element is removed. Event
* observers on the original element will be lost.
*
* @param element
* element or element id
* @since 5.2.0
*/
replaceElementTagName : function(element, newTagName) {
element = $(element);
var tag = element.tagName;
/* outerHTML is IE only; this simulates it on any browser. */
var dummy = document.createElement('html');
dummy.appendChild(element.cloneNode(true));
var outerHTML = dummy.innerHTML;
var replaceHTML = outerHTML.replace(new RegExp("^<" + tag, "i"),
"<" + newTagName).replace(new RegExp("" + tag + ">$", "i"),
"" + newTagName + ">");
element.insert( {
before : replaceHTML
});
Tapestry.remove(element);
},
/**
* Removes an element and all of its direct and indirect children. The
* element is first purged, to ensure that Internet Explorer doesn't leak
* memory if event handlers associated with the element (or its children)
* have references back to the element.
*
* @since 5.2.0
*/
remove : function(element) {
Tapestry.purge(element);
Element.remove(element);
},
/**
* Purges the element of any event handlers (necessary in IE to ensure that
* memory leaks do not occur, and harmless in other browsers). The element
* is purged, then any children of the element are purged.
*/
purge : function(element) {
/* Adapted from http://javascript.crockford.com/memory/leak.html */
var attrs = element.attributes;
if (attrs) {
var i, name;
for (i = attrs.length - 1; i >= 0; i--) {
if (attrs[i]) {
name = attrs[i].name;
/* Looking for onclick, etc. */
if (typeof element[name] == 'function') {
element[name] = null;
}
}
}
}
/* Get rid of any Prototype event handlers as well. */
Event.stopObserving(element);
Tapestry.purgeChildren(element);
},
/**
* Invokes purge() on all the children of the element.
*/
purgeChildren : function(element) {
var children = element.childNodes;
if (children) {
var l = children.length, i, child;
for (i = 0; i < l; i++) {
var child = children[i];
/* Just purge element nodes, not text, etc. */
if (child.nodeType == 1)
Tapestry.purge(children[i]);
}
}
}
};
Element.addMethods( {
/**
* Works upward from the element, checking to see if the element is visible.
* Returns false if it finds an invisible container. Returns true if it
* makes it as far as a (visible) FORM element.
*
* Note that this only applies to the CSS definition of visible; it doesn't
* check that the element is scrolled into view.
*
* @param element
* to search up from
* @return true if visible (and containers visible), false if it or
* container are not visible
*/
isDeepVisible : function(element) {
var current = $(element);
while (true) {
if (!current.visible())
return false;
if (current.tagName == "FORM")
break;
current = $(current.parentNode);
}
return true;
},
/**
* Observes an event and turns it into a Tapestry.ACTION_EVENT. The original
* event is stopped. The original event object is passed as the memo when
* the action event is fired. This allows the logic for clicking an element
* to be separated from the logic for processing that click event, which is
* often useful when the click logic needs to be intercepted, or when the
* action logic needs to be triggered outside the context of a DOM event.
*
* $T(element).hasAction will be true after invoking this method.
*
* @param element
* to observe events from
* @param eventName
* name of event to observer, typically "click"
* @param handler
* function to be invoked; it will be registered as a observer of
* the Tapestry.ACTION_EVENT.
*/
observeAction : function(element, eventName, handler) {
element.observe(eventName, function(event) {
event.stop();
element.fire(Tapestry.ACTION_EVENT, event);
});
element.observe(Tapestry.ACTION_EVENT, handler);
$T(element).hasAction = true;
}
});
Element
.addMethods(
'FORM',
{
/**
* Gets the Tapestry.FormEventManager for the form.
*
* @param form
* form element
*/
getFormEventManager : function(form) {
form = $(form);
var manager = $T(form).formEventManager;
if (manager == undefined) {
throw "No Tapestry.FormEventManager object has been created for form '#{id}'."
.interpolate(form);
}
return manager;
},
/**
* Identifies in the form what is the cause of the
* submission. The element's id is stored into the t:submit
* hidden field (created as needed).
*
* @param form
* to update
* @param element
* id or element that is the cause of the submit
* (a Submit or LinkSubmit)
*/
setSubmittingElement : function(form, element) {
form.getFormEventManager()
.setSubmittingElement(element);
},
/**
* Turns off client validation for the next submission of
* the form.
*/
skipValidation : function(form) {
$T(form).skipValidation = true;
},
/**
* Programmatically perform a submit, invoking the onsubmit
* event handler (if present) before calling form.submit().
*/
performSubmit : function(form, event) {
if (form.onsubmit == undefined
|| form.onsubmit.call(window.document, event)) {
form.submit();
}
},
/**
* Sends an Ajax request to the Form's action. This
* encapsulates a few things, such as a default onFailure
* handler, and working around bugs/features in Prototype
* concerning how submit buttons are processed.
*
* @param form
* used to define the data to be sent in the
* request
* @param options
* standard Prototype Ajax Options
* @return Ajax.Request the Ajax.Request created for the
* request
*/
sendAjaxRequest : function(form, url, options) {
form = $(form);
/*
* Generally, options should not be null or missing,
* because otherwise there's no way to provide any
* callbacks!
*/
options = Object.clone(options || {});
/*
* Find the elements, skipping over any submit buttons.
* This works around bugs in Prototype 1.6.0.2.
*/
var elements = form.getElements().reject(function(e) {
return e.tagName == "INPUT" && e.type == "submit";
});
var hash = Form.serializeElements(elements, true);
/*
* Copy the parameters in, overwriting field values,
* because Prototype 1.6.0.2 does not.
*/
Object.extend(hash, options.parameters);
options.parameters = hash;
/*
* Ajax.Request will convert the hash into a query
* string and post it.
*/
return Tapestry.ajaxRequest(url, options);
}
});
Element.addMethods( [ 'INPUT', 'SELECT', 'TEXTAREA' ], {
/**
* Invoked on a form element (INPUT, SELECT, etc.), gets or creates the
* Tapestry.FieldEventManager for that field.
*
* @param field
* field element
*/
getFieldEventManager : function(field) {
field = $(field);
var t = $T(field);
var manager = t.fieldEventManager;
if (manager == undefined) {
manager = new Tapestry.FieldEventManager(field);
t.fieldEventManager = manager;
}
return manager;
},
/**
* Obtains the Tapestry.FieldEventManager and asks it to show the validation
* message. Sets the validationError property of the elements tapestry
* object to true.
*
* @param element
* @param message
* to display
*/
showValidationMessage : function(element, message) {
element = $(element);
element.getFieldEventManager().showValidationMessage(message);
return element;
},
/**
* Removes any validation decorations on the field, and hides the error
* popup (if any) for the field.
*/
removeDecorations : function(element) {
$(element).getFieldEventManager().removeDecorations();
return element;
},
/**
* Adds a standard validator for the element, an observer of
* Tapestry.FIELD_VALIDATE_EVENT. The validator function will be passed the
* current field value and should throw an error message if the field's
* value is not valid.
*
* @param element
* field element to validate
* @param validator
* function to be passed the field value
*/
addValidator : function(element, validator) {
element.observe(Tapestry.FIELD_VALIDATE_EVENT, function(event) {
try {
validator.call(this, event.memo.translated);
} catch (message) {
element.showValidationMessage(message);
}
});
return element;
}
});
/** Container of functions that may be invoked by the Tapestry.init() function. */
Tapestry.Initializer = {
/** Make the given field the active field (focus on the field). */
activate : function(id) {
$(id).activate();
},
/**
* evalScript is a synonym for the JavaScript eval function. It is used in
* Ajax requests to handle any setup code that does not fit into a standard
* Tapestry.Initializer call.
*/
evalScript : eval,
ajaxFormLoop : function(spec) {
var rowInjector = $(spec.rowInjector);
$(spec.addRowTriggers).each(function(triggerId) {
$(triggerId).observeAction("click", function(event) {
$(rowInjector).trigger();
});
});
},
formLoopRemoveLink : function(spec) {
var link = $(spec.link);
var fragmentId = spec.fragment;
link.observeAction("click", function(event) {
var successHandler = function(transport) {
var container = $(fragmentId);
var effect = Tapestry.ElementEffect.fade(container);
effect.options.afterFinish = function() {
Tapestry.remove(container);
}
};
Tapestry.ajaxRequest(spec.url, successHandler);
});
},
/**
* Convert a form or link into a trigger of an Ajax update that updates the
* indicated Zone.
*
* @param spec.linkId
* id or instance of